/*
 * Copyright 2007-2009 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.paoding.rose.web.impl.module;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import net.paoding.rose.RoseConstants;
import net.paoding.rose.impl.scanning.ModuleScanner;
import net.paoding.rose.impl.util.SpringUtils;
import net.paoding.rose.web.ControllerErrorHandler;
import net.paoding.rose.web.ControllerInterceptor;
import net.paoding.rose.web.annotation.Controller;
import net.paoding.rose.web.annotation.Ignored;
import net.paoding.rose.web.annotation.Interceptor;
import net.paoding.rose.web.annotation.ReqMapping;
import net.paoding.rose.web.annotation.ReqMethod;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.UrlResource;
import org.springframework.util.ClassUtils;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.XmlWebApplicationContext;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;

/**
 * 根据输入的module类信息，构造出具体的Module结构出来
 * 
 * @author qieqie.wang@paoding.net
 * 
 */
@SuppressWarnings("deprecation")
public class ModulesBuilder {

	private Log logger = LogFactory.getLog(getClass());

	public List<Module> build(XmlWebApplicationContext rootContext,
			ModuleScanner moduleScanner) {
		List<Module> modules = new ArrayList<Module>(moduleScanner
				.getModulePaths().size());
		for (String modulePath : moduleScanner.getModulePaths()) {
			final List<URL> contextResources = moduleScanner
					.getContextResouces(modulePath);
			XmlWebApplicationContext context = rootContext;
			if (contextResources.size() != 0) {
				context = new XmlWebApplicationContext() {
					protected void loadBeanDefinitions(
							XmlBeanDefinitionReader reader)
							throws BeansException, IOException {
						for (URL url : contextResources) {
							reader.loadBeanDefinitions(new UrlResource(url));
						}
					}
				};
				((XmlWebApplicationContext) context).setParent(rootContext);
				((XmlWebApplicationContext) context)
						.setServletContext(rootContext.getServletContext());
				((XmlWebApplicationContext) context).refresh();
			}
			context.getServletContext().setAttribute(
					WebApplicationContext.class.getName() + ".module_"
							+ modulePath, context);

			// 从Spring应用环境中找出全局共有的拦截器，一会要设进去到各个Module中
			ArrayList<NamedControllerInterceptor> globalInterceptors = findGlobaInterceptors(context);
			// 找出默认的控制器异常处理类，如果模块没有定义自己的异常处理类，则使用它
			ControllerErrorHandler defaultErrorHandler = getDefaultErrorHandler(context);

			ModuleBean module = new ModuleBean(modulePath);

			// multipart resolver
			module.setMultipartResolver(initMultipartResolver(context));

			// 将全局的拦截器设置到module中
			for (NamedControllerInterceptor namedInterceptor : globalInterceptors) {
				module.addNamedControllerInterceptor(namedInterceptor);
				if (logger.isDebugEnabled()) {
					logger.debug("module '" + module.getPath()
							+ "': apply global intercetpor "
							+ namedInterceptor.getName() + " to module '"
							+ module.getPath() + "'");
				}
			}

			// 扫描找到的类...
			for (Class<?> clazz : moduleScanner.getModuleClasses(modulePath)) {
				if (Modifier.isAbstract(clazz.getModifiers())
						|| Modifier.isInterface(clazz.getModifiers())
						|| !Modifier.isPublic(clazz.getModifiers())) {
					if (logger.isDebugEnabled()) {
						logger.debug("Ignored by its modififer:"
								+ clazz.getName());
					}
					continue;
				}
				if (clazz.getAnnotation(Ignored.class) != null) {
					if (logger.isDebugEnabled()) {
						logger.debug("Ignored by annoation @"
								+ Ignored.class.getName() + ":"
								+ clazz.getName());
					}
					continue;
				}
				// 在这里try-catche使得一个错误不去影响其它类
				try {
					checkController(context, module, clazz);
					checkInterceptor(context, module, clazz);
					checkErrorHandler(context, module, clazz);
				} catch (Throwable e) {
					logger.error("fail to load/initiate class: "
							+ clazz.getName(), e);
				}
			}
			if (module.getErrorHandler() == null && defaultErrorHandler != null) {
				module.setErrorHandler(defaultErrorHandler);
				if (logger.isInfoEnabled()) {
					logger.info("set default errorHandler for module: "
							+ module.getPath());
				}
			} else {
				if (logger.isDebugEnabled()) {
					logger.debug("not found errorHandler for module: "
							+ module.getPath());
				}
			}
			// 放进去以返回
			modules.add(module);
		}
		return modules;
	}

	private ControllerErrorHandler getDefaultErrorHandler(
			ApplicationContext context) {
		ControllerErrorHandler defaultErrorHandler = null;
		String[] names = context
				.getBeanNamesForType(ControllerErrorHandler.class);
		for (int i = 0; defaultErrorHandler == null && i < names.length; i++) {
			defaultErrorHandler = (ControllerErrorHandler) context
					.getBean(names[i]);
			if (defaultErrorHandler.getClass().getAnnotation(Ignored.class) == null) {
				logger.debug("ignored controllerErrorHandler"
						+ " in application context: " + defaultErrorHandler);
				defaultErrorHandler = null;
			} else {
				break;
			}
		}
		return defaultErrorHandler;
	}

	private ArrayList<NamedControllerInterceptor> findGlobaInterceptors(
			XmlWebApplicationContext context) {
		String[] globalInterceptorNames = SpringUtils.getBeanNames(context
				.getBeanFactory(), ControllerInterceptor.class);
		ArrayList<NamedControllerInterceptor> globalInterceptors = new ArrayList<NamedControllerInterceptor>(
				globalInterceptorNames.length);
		for (String beanName : globalInterceptorNames) {
			ControllerInterceptor interceptor = (ControllerInterceptor) context
					.getBean(beanName);
			if (interceptor.getClass().getAnnotation(Ignored.class) != null) {
				if (logger.isDebugEnabled()) {
					logger.debug("Ignored context interceptor:" + interceptor);
				}
				continue;
			}
			String name = asShortPropertyName(beanName, "Interceptor");
			globalInterceptors.add(new NamedControllerInterceptor(name,
					interceptor, true));
			if (logger.isDebugEnabled()) {
				logger.debug("context interceptor[" + interceptor.getPriority() + "]: " // \r\n
						+ name + "=" + interceptor.getClass().getName());
			}
		}
		return globalInterceptors;
	}

	protected boolean checkController(ApplicationContext context,
			ModuleBean module, Class<?> clazz) throws InstantiationException,
			IllegalAccessException {
		if (clazz.getName().endsWith(RoseConstants.CONTROLLER_SUFFIX)) {
			String[] controllerPaths = null;
			ReqMethod[] methods;
			ReqMapping reqMappingAnnotation = clazz.getAnnotation(ReqMapping.class);
			if (reqMappingAnnotation != null) {
				methods = reqMappingAnnotation.methods();
				controllerPaths = reqMappingAnnotation.path();
			} else {
				methods = new ReqMethod[] { ReqMethod.ALL };
				Controller controllerAnnotation = clazz.getAnnotation(Controller.class);
				if (controllerAnnotation != null) {
					controllerPaths = controllerAnnotation.path();
				}
			}
			if (controllerPaths != null) {
				// 如果controllerPaths.length==0，表示没有任何path可以映射到这个controller了
				for (int i = 0; i < controllerPaths.length; i++) {
					if (ReqMapping.DEFAULT_PATH.equals(controllerPaths[i])) {
						String shortName = ClassUtils.getShortNameAsProperty(clazz);
						controllerPaths[i] = "/" + shortName.substring(0, shortName.length()
								- RoseConstants.CONTROLLER_SUFFIX.length());
					} else if (controllerPaths[i].length() > 0 && controllerPaths[i].charAt(0) != '/') {
						controllerPaths[i] = '/' + controllerPaths[i];
					} else if (controllerPaths[i].equals("/")) {
						controllerPaths[i] = "";
					}
				}
			} else {
				String shortName = ClassUtils.getShortNameAsProperty(clazz);
				controllerPaths = new String[]{ "/"
						+ shortName.substring(0, shortName.length()
								- RoseConstants.CONTROLLER_SUFFIX.length())};
			}
			// 这个Controller是否已经在Context中配置了?
			// 如果使用Context配置，就不需要在这里实例化
			Object controller = SpringUtils.getBean(context, clazz);
			Object rawController = null;
			if (controller != null) {
				if (clazz.isAssignableFrom(controller.getClass())) {
					rawController = controller;
				}
			} else {
				rawController = clazz.newInstance();
				controller = SpringUtils.autowire(rawController, context);
			}
			for (int i = 0; i < controllerPaths.length; i++) {
				if (methods.length > 0) {
					module.addController(controllerPaths[i], methods, clazz, controller);
				}
			}
			if (Proxy.isProxyClass(controller.getClass())) {
				if (logger.isDebugEnabled()) {
					logger.debug("module '" + module.getPath()
							+ "': add controller " + Arrays.toString(controllerPaths) + "= proxy of "
							+ clazz.getName());
				}
			} else {
				if (logger.isDebugEnabled()) {
					logger.debug("module '" + module.getPath()
							+ "': add controller " + Arrays.toString(controllerPaths) + "= "
							+ controller.getClass().getName());
				}
			}
			if (rawController != null) {
				if (controllerPaths.length > 0) {
					// 实现注意：内部拦截器对控制器字段的引用，最好使用控制器提供getXxx()方法获取，而非直接获取!
					checkInnerInterceptors(module, clazz, controllerPaths[0],
							controller, rawController);
				}
			} else {
				if (logger.isDebugEnabled()) {
					logger.debug("skip checking controller inner intercetpors: " + clazz.getName());
				}
			}
			return true;
		}
		return false;
	}

	private void checkInnerInterceptors(ModuleBean module, Class<?> clazz,
			String controllerPath, Object controller, Object rawController)
			throws IllegalAccessException {
		// 控制器特有的拦截器
		Class<?> _clazz = clazz;
		while (true) {
			if (_clazz == Object.class || _clazz == null
					|| Modifier.isInterface(_clazz.getModifiers())) {
				break;
			}
			Field[] fields = _clazz.getDeclaredFields();
			for (Field field : fields) {
				// 只对本类声明或父类声明的公共或保护的字段(也就是子类可以引用的字段)
				// !!和控制器是否暴露父类的方法不一样的地方，这里不需要父类声明@AsSuperController标注
				if (clazz == _clazz || Modifier.isPublic(field.getModifiers())
						|| Modifier.isProtected(field.getModifiers())) {
					if (Modifier.isStatic(field.getModifiers())) {
						continue;
					}
					// 
					if (field.getAnnotation(Ignored.class) == null) {
						if (!ControllerInterceptor.class.isAssignableFrom(field
								.getClass())) {

						}
						field.setAccessible(true);
						Object fieldValue = field.get(rawController);
						if (fieldValue == null) {
							continue;
						}
						if (fieldValue instanceof ControllerInterceptor) {
							NamedControllerInterceptor named = new NamedControllerInterceptor(
									controllerPath + "." + field.getName(),
									(ControllerInterceptor) fieldValue);
							named.setController(controller);
							module.addNamedControllerInterceptor(named);
							if (logger.isDebugEnabled()) {
								logger.debug("module '" + module.getPath()
										+ "': add intercetpor ["
										+ named.getPriority() + "]:" + named.getName()
										+ "=" + fieldValue.getClass().getName());
							}
						} else {
							ArrayList<ControllerInterceptor> list = new ArrayList<ControllerInterceptor>();
							if (fieldValue.getClass().isArray()
									&& fieldValue
											.getClass()
											.getComponentType()
											.isAssignableFrom(
													ControllerInterceptor.class)) {
								ControllerInterceptor[] array = (ControllerInterceptor[]) fieldValue;
								for (ControllerInterceptor object : array) {
									if (object != null) {
										list.add(object);
									}
								}
							} else if (fieldValue instanceof Iterable<?>) {
								for (Object elem : (Iterable<?>) fieldValue) {
									if (elem != null
											&& !(elem instanceof ControllerInterceptor)) {
										list.clear();
										break;
									}
									if (elem != null) {
										list.add((ControllerInterceptor) elem);
									}
								}
							}
							// 具有相同名字的多个拦截器
							for (ControllerInterceptor controllerInterceptor : list) {
								NamedControllerInterceptor named = new NamedControllerInterceptor(
										controllerPath + "." + field.getName(),
										controllerInterceptor);
								named.setController(controller);
								module.addNamedControllerInterceptor(named);
								if (logger.isDebugEnabled()) {
									logger.debug("module '"
											+ module.getPath()
											+ "': add intercetpor ["
											+ controllerInterceptor.getPriority() 
											+ "]="
											+ named.getName()
											+ controllerInterceptor.getClass()
													.getName());
								}
							}
						}
					}
				}
			}
			_clazz = _clazz.getSuperclass();
		}
	}

	protected boolean checkInterceptor(ApplicationContext context,
			ModuleBean module, Class<?> clazz) throws InstantiationException,
			IllegalAccessException {
		if (ControllerInterceptor.class.isAssignableFrom(clazz)) {
			Interceptor annotation = clazz.getAnnotation(Interceptor.class);
			ControllerInterceptor rawInterceptor = (ControllerInterceptor) clazz
					.newInstance();
			ControllerInterceptor interceptor = (ControllerInterceptor) SpringUtils
					.autowire(rawInterceptor, context);
			NamedControllerInterceptor named = new NamedControllerInterceptor(
					"", interceptor);
			if (annotation != null
					&& StringUtils.isNotBlank(annotation.value())) {
				named.setName(annotation.value());
			} else {
				named.setName(asShortPropertyName(clazz.getSimpleName(),
						"Interceptor"));
			}
			if (named.getName().indexOf('.') != -1) {
				throw new IllegalArgumentException("请不要使用含有点号(.)的'"
						+ named.getName() + "'作为拦截器的名字");
			}
			module.addNamedControllerInterceptor(named);
			if (Proxy.isProxyClass(interceptor.getClass())) {
				if (logger.isDebugEnabled()) {
					logger.debug("module '" + module.getPath()
							+ "': add intercetpor ["
							+ interceptor.getPriority() + "]:"
							+ named.getName()
							+ "= proxy of " + clazz.getName());
				}
			} else {
				if (logger.isDebugEnabled()) {
					logger.debug("module '" + module.getPath()
							+ "': add intercetpor [" 
							+ interceptor.getPriority() + "]:"
							+ named.getName() + "="
							+ interceptor.getClass().getName());
				}
			}

			return true;
		}
		return false;
	}

	private boolean checkErrorHandler(ApplicationContext context,
			ModuleBean module, Class<?> clazz) throws InstantiationException,
			IllegalAccessException {
		if (ControllerErrorHandler.class.isAssignableFrom(clazz)) {
			ControllerErrorHandler errorHandler = (ControllerErrorHandler) SpringUtils
					.createBean(clazz, context);
			module.setErrorHandler(errorHandler);
			if (logger.isInfoEnabled()) {
				logger.info("module '" + module.getPath() + "' add error handler " + errorHandler);
			}
			return true;
		}
		return false;
	}

	protected MultipartResolver initMultipartResolver(ApplicationContext context) {
		MultipartResolver multipartResolver = (MultipartResolver) SpringUtils
				.getBean(context, MultipartResolver.class);
		if (multipartResolver != null) {
			if (logger.isDebugEnabled()) {
				logger.debug("Using MultipartResolver [" + multipartResolver
						+ "]");
			}
		} else {
			multipartResolver = new CommonsMultipartResolver();
			if (logger.isDebugEnabled()) {
				logger.debug("No found MultipartResolver in context, "
						+ "Using MultipartResolver by default ["
						+ multipartResolver + "]");
			}
		}
		return multipartResolver;
	}

	static String asShortPropertyName(String beanName, String suffixToRemove) {
		beanName = org.springframework.util.StringUtils.unqualify(beanName);
		beanName = org.springframework.util.StringUtils.uncapitalize(beanName);
		if (suffixToRemove != null && beanName.endsWith(suffixToRemove)) {
			beanName = beanName.substring(0, beanName.length()
					- suffixToRemove.length());
		}
		return beanName;
	}
}
